// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/command_line.h"
#include "base/strings/utf_string_conversions.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/notification_types.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/test_utils.h"
#include "content/shell/browser/shell.h"
#include "content/test/content_browser_test_utils_internal.h"
#include "net/dns/mock_host_resolver.h"
#include "third_party/WebKit/public/web/WebFindOptions.h"

namespace content {

namespace {

    const int kInvalidId = -1;

    // The results of a find request.
    struct FindResults {
        FindResults(int request_id, int number_of_matches, int active_match_ordinal)
            : request_id(request_id)
            , number_of_matches(number_of_matches)
            , active_match_ordinal(active_match_ordinal)
        {
        }
        FindResults()
            : FindResults(kInvalidId, 0, 0)
        {
        }

        int request_id;
        int number_of_matches;
        int active_match_ordinal;
    };

} // namespace

class TestWebContentsDelegate : public WebContentsDelegate {
public:
    TestWebContentsDelegate()
        : last_request_id_(kInvalidId)
        , last_finished_request_id_(kInvalidId)
        , next_reply_received_(false)
        , record_replies_(false)
        , waiting_for_(NOTHING)
    {
    }
    ~TestWebContentsDelegate() override { }

    // Returns the current find results.
    const FindResults& GetFindResults() const
    {
        return current_results_;
    }

    // Waits for all pending replies to be received.
    void WaitForFinalReply()
    {
        if (last_finished_request_id_ >= last_request_id_)
            return;

        WaitFor(FINAL_REPLY);
    }

    // Waits for the next find reply. This is useful for waiting for a single
    // match to be activated, or for a new frame to be searched.
    void WaitForNextReply()
    {
        if (next_reply_received_)
            return;

        WaitFor(NEXT_REPLY);
    }

    // Indicates that the next find reply from this point will be the one to wait
    // for when WaitForNextReply() is called. It may be the case that the reply
    // comes before the call to WaitForNextReply(), in which case it will return
    // immediately.
    void MarkNextReply()
    {
        next_reply_received_ = false;
    }

    // Called when a new find request is issued, so the delegate knows the last
    // request ID.
    void UpdateLastRequest(int request_id)
    {
        last_request_id_ = request_id;
    }

    // From when this function is called, all replies coming in via FindReply()
    // will be recorded. These replies can be retrieved via GetReplyRecord().
    void StartReplyRecord()
    {
        reply_record_.clear();
        record_replies_ = true;
    }

    // Retreives the results from the find replies recorded since the last call to
    // StartReplyRecord(). Calling this function also stops the recording new find
    // replies.
    const std::vector<FindResults>& GetReplyRecord()
    {
        record_replies_ = false;
        return reply_record_;
    }

#if defined(OS_ANDROID)
    // Waits for all of the find match rects to be received.
    void WaitForMatchRects()
    {
        WaitFor(MATCH_RECTS);
    }

    const std::vector<gfx::RectF>& find_match_rects() const
    {
        return find_match_rects_;
    }

    const gfx::RectF& active_match_rect() const
    {
        return active_match_rect_;
    }
#endif

private:
    enum WaitingFor {
        NOTHING,
        FINAL_REPLY,
        NEXT_REPLY,
#if defined(OS_ANDROID)
        MATCH_RECTS
#endif
    };

    // WebContentsDelegate override.
    void FindReply(WebContents* web_contents,
        int request_id,
        int number_of_matches,
        const gfx::Rect& selection_rect,
        int active_match_ordinal,
        bool final_update) override
    {
        if (record_replies_) {
            reply_record_.emplace_back(
                request_id, number_of_matches, active_match_ordinal);
        }

        // Update the current results.
        if (request_id > current_results_.request_id)
            current_results_.request_id = request_id;
        if (number_of_matches != -1)
            current_results_.number_of_matches = number_of_matches;
        if (active_match_ordinal != -1)
            current_results_.active_match_ordinal = active_match_ordinal;

        if (!final_update)
            return;

        if (request_id > last_finished_request_id_)
            last_finished_request_id_ = request_id;
        next_reply_received_ = true;

        // If we are waiting for this find reply, stop waiting.
        if (waiting_for_ == NEXT_REPLY || (waiting_for_ == FINAL_REPLY && last_finished_request_id_ >= last_request_id_)) {
            StopWaiting();
        }
    }

    // Uses |message_loop_runner_| to wait for various things.
    void WaitFor(WaitingFor wait_for)
    {
        ASSERT_EQ(NOTHING, waiting_for_);
        ASSERT_NE(NOTHING, wait_for);

        // Wait for |wait_for|.
        waiting_for_ = wait_for;
        message_loop_runner_ = new content::MessageLoopRunner;
        message_loop_runner_->Run();

        // Done waiting.
        waiting_for_ = NOTHING;
        message_loop_runner_ = nullptr;
    }

    // Stop waiting for |waiting_for_|.
    void StopWaiting()
    {
        if (!message_loop_runner_.get())
            return;

        ASSERT_NE(NOTHING, waiting_for_);
        message_loop_runner_->Quit();
    }

#if defined(OS_ANDROID)
    // WebContentsDelegate override.
    void FindMatchRectsReply(WebContents* web_contents,
        int version,
        const std::vector<gfx::RectF>& rects,
        const gfx::RectF& active_rect) override
    {
        // Update the current rects.
        find_match_rects_ = rects;
        active_match_rect_ = active_rect;

        // If we are waiting for match rects, stop waiting.
        if (waiting_for_ == MATCH_RECTS)
            StopWaiting();
    }

    std::vector<gfx::RectF> find_match_rects_;

    gfx::RectF active_match_rect_;
#endif

    // The latest known results from the current find request.
    FindResults current_results_;

    // The ID of the last find request issued.
    int last_request_id_;

    // The ID of the last find request to finish (all replies received).
    int last_finished_request_id_;

    // Indicates whether the next reply after MarkNextReply() has been received.
    bool next_reply_received_;

    // Indicates whether the find results from incoming find replies are currently
    // being recorded.
    bool record_replies_;

    // A record of all find replies that have come in via FindReply() since
    // StartReplyRecor() was last called.
    std::vector<FindResults> reply_record_;

    // Indicates what |message_loop_runner_| is waiting for, if anything.
    WaitingFor waiting_for_;

    scoped_refptr<content::MessageLoopRunner> message_loop_runner_;

    DISALLOW_COPY_AND_ASSIGN(TestWebContentsDelegate);
};

class FindRequestManagerTest : public ContentBrowserTest,
                               public testing::WithParamInterface<bool> {
public:
    FindRequestManagerTest()
        : normal_delegate_(nullptr)
        , last_request_id_(0)
    {
    }
    ~FindRequestManagerTest() override { }

    void SetUpOnMainThread() override
    {
        host_resolver()->AddRule("*", "127.0.0.1");
        ASSERT_TRUE(embedded_test_server()->Start());

        // Swap the WebContents's delegate for our test delegate.
        normal_delegate_ = contents()->GetDelegate();
        contents()->SetDelegate(&test_delegate_);
    }

    void TearDownOnMainThread() override
    {
        // Swap the WebContents's delegate back to its usual delegate.
        contents()->SetDelegate(normal_delegate_);
    }

#if !defined(OS_ANDROID)
    void SetUpCommandLine(base::CommandLine* command_line) override
    {
        IsolateAllSitesForTesting(command_line);
    }
#endif

protected:
    // Navigates to |url| and waits for it to finish loading.
    void LoadAndWait(const std::string& url)
    {
        TestNavigationObserver navigation_observer(contents());
        NavigateToURL(shell(), embedded_test_server()->GetURL("a.com", url));
        EXPECT_TRUE(navigation_observer.last_navigation_succeeded());
    }

    // Loads a multi-frame page. The page will have a full binary frame tree of
    // height |height|. If |cross_process| is true, child frames will be loaded
    // cross-process.
    void LoadMultiFramePage(int height, bool cross_process)
    {
        LoadAndWait("/find_in_page_multi_frame.html");
        FrameTreeNode* root = contents()->GetFrameTree()->root();
        LoadMultiFramePageChildFrames(height, cross_process, root);
    }

    // Reloads the child frame cross-process.
    void MakeChildFrameCrossProcess()
    {
        FrameTreeNode* root = contents()->GetFrameTree()->root();
        FrameTreeNode* child = root->child_at(0);
        GURL url(embedded_test_server()->GetURL(
            "b.com", child->current_url().path()));

        TestNavigationObserver observer(shell()->web_contents());
        NavigateFrameToURL(child, url);
        EXPECT_EQ(url, observer.last_navigation_url());
        EXPECT_TRUE(observer.last_navigation_succeeded());
    }

    void Find(const std::string& search_text,
        const blink::WebFindOptions& options)
    {
        delegate()->UpdateLastRequest(++last_request_id_);
        contents()->Find(last_request_id_,
            base::UTF8ToUTF16(search_text),
            options);
    }

    WebContentsImpl* contents() const
    {
        return static_cast<WebContentsImpl*>(shell()->web_contents());
    }

    TestWebContentsDelegate* delegate() const
    {
        return static_cast<TestWebContentsDelegate*>(contents()->GetDelegate());
    }

    int last_request_id() const
    {
        return last_request_id_;
    }

private:
    // Helper function for LoadMultiFramePage. Loads child frames until the frame
    // tree rooted at |root| is a full binary tree of height |height|.
    void LoadMultiFramePageChildFrames(int height,
        bool cross_process,
        FrameTreeNode* root)
    {
        if (height == 0)
            return;

        std::string hostname = root->current_origin().host();
        if (cross_process)
            hostname.insert(0, 1, 'a');
        GURL url(embedded_test_server()->GetURL(hostname,
            "/find_in_page_multi_frame.html"));

        TestNavigationObserver observer(shell()->web_contents());

        FrameTreeNode* child = root->child_at(0);
        NavigateFrameToURL(child, url);
        EXPECT_TRUE(observer.last_navigation_succeeded());
        LoadMultiFramePageChildFrames(height - 1, cross_process, child);

        child = root->child_at(1);
        NavigateFrameToURL(child, url);
        EXPECT_TRUE(observer.last_navigation_succeeded());
        LoadMultiFramePageChildFrames(height - 1, cross_process, child);
    }

    TestWebContentsDelegate test_delegate_;
    WebContentsDelegate* normal_delegate_;

    // The ID of the last find request requested.
    int last_request_id_;

    DISALLOW_COPY_AND_ASSIGN(FindRequestManagerTest);
};

// Frames are made cross-process when the test param is set to
// true. Cross-process frames are not used on android.
#if defined(OS_ANDROID)
INSTANTIATE_TEST_CASE_P(
    FindRequestManagerTests, FindRequestManagerTest, testing::Values(false));
#else
INSTANTIATE_TEST_CASE_P(
    FindRequestManagerTests, FindRequestManagerTest, testing::Bool());
#endif

// TODO(crbug.com/615291): These tests frequently fail on Android.
#if defined(OS_ANDROID)
#define MAYBE(x) DISABLED_##x
#else
#define MAYBE(x) x
#endif

// Tests basic find-in-page functionality (such as searching forward and
// backward) and check for correct results at each step.
IN_PROC_BROWSER_TEST_P(FindRequestManagerTest, MAYBE(Basic))
{
    LoadAndWait("/find_in_page.html");
    if (GetParam())
        MakeChildFrameCrossProcess();

    blink::WebFindOptions options;
    Find("result", options);
    delegate()->WaitForFinalReply();

    FindResults results = delegate()->GetFindResults();
    EXPECT_EQ(last_request_id(), results.request_id);
    EXPECT_EQ(19, results.number_of_matches);
    EXPECT_EQ(1, results.active_match_ordinal);

    options.findNext = true;
    for (int i = 2; i <= 10; ++i) {
        Find("result", options);
        delegate()->WaitForFinalReply();

        results = delegate()->GetFindResults();
        EXPECT_EQ(last_request_id(), results.request_id);
        EXPECT_EQ(19, results.number_of_matches);
        EXPECT_EQ(i, results.active_match_ordinal);
    }

    options.forward = false;
    for (int i = 9; i >= 5; --i) {
        Find("result", options);
        delegate()->WaitForFinalReply();

        results = delegate()->GetFindResults();
        EXPECT_EQ(last_request_id(), results.request_id);
        EXPECT_EQ(19, results.number_of_matches);
        EXPECT_EQ(i, results.active_match_ordinal);
    }
}

// Tests searching for a word character-by-character, as would typically be done
// by a user typing into the find bar.
IN_PROC_BROWSER_TEST_P(FindRequestManagerTest, MAYBE(CharacterByCharacter))
{
    LoadAndWait("/find_in_page.html");
    if (GetParam())
        MakeChildFrameCrossProcess();

    blink::WebFindOptions default_options;
    Find("r", default_options);
    Find("re", default_options);
    Find("res", default_options);
    Find("resu", default_options);
    Find("resul", default_options);
    Find("result", default_options);
    delegate()->WaitForFinalReply();

    FindResults results = delegate()->GetFindResults();
    EXPECT_EQ(last_request_id(), results.request_id);
    EXPECT_EQ(19, results.number_of_matches);
    EXPECT_EQ(1, results.active_match_ordinal);
}

// TODO(crbug.com/615291): This test frequently fails on Android.
// TODO(crbug.com/674742): This test is flaky on Win
#if defined(OS_ANDROID) || defined(OS_WIN)
#define MAYBE_RapidFire DISABLED_RapidFire
#else
#define MAYBE_RapidFire RapidFire
#endif

// Tests sending a large number of find requests subsequently.
IN_PROC_BROWSER_TEST_P(FindRequestManagerTest, MAYBE_RapidFire)
{
    LoadAndWait("/find_in_page.html");
    if (GetParam())
        MakeChildFrameCrossProcess();

    blink::WebFindOptions options;
    Find("result", options);

    options.findNext = true;
    for (int i = 2; i <= 1000; ++i)
        Find("result", options);
    delegate()->WaitForFinalReply();

    FindResults results = delegate()->GetFindResults();
    EXPECT_EQ(last_request_id(), results.request_id);
    EXPECT_EQ(19, results.number_of_matches);
    EXPECT_EQ(last_request_id() % results.number_of_matches,
        results.active_match_ordinal);
}

// Tests removing a frame during a find session.
// TODO(crbug.com/657331): Test is flaky on all platforms.
IN_PROC_BROWSER_TEST_P(FindRequestManagerTest, DISABLED_RemoveFrame)
{
    LoadMultiFramePage(2 /* height */, GetParam() /* cross_process */);

    blink::WebFindOptions options;
    Find("result", options);
    delegate()->WaitForFinalReply();
    options.findNext = true;
    options.forward = false;
    Find("result", options);
    Find("result", options);
    Find("result", options);
    Find("result", options);
    Find("result", options);
    delegate()->WaitForFinalReply();

    FindResults results = delegate()->GetFindResults();
    EXPECT_EQ(last_request_id(), results.request_id);
    EXPECT_EQ(21, results.number_of_matches);
    EXPECT_EQ(17, results.active_match_ordinal);

    // Remove a frame.
    FrameTreeNode* root = contents()->GetFrameTree()->root();
    root->RemoveChild(root->child_at(0));

    // The number of matches and active match ordinal should update automatically
    // to exclude the matches from the removed frame.
    results = delegate()->GetFindResults();
    EXPECT_EQ(12, results.number_of_matches);
    EXPECT_EQ(8, results.active_match_ordinal);
}

// Tests adding a frame during a find session.
// TODO(crbug.com/657331): Test is flaky on all platforms.
IN_PROC_BROWSER_TEST_P(FindRequestManagerTest, DISABLED_AddFrame)
{
    LoadMultiFramePage(2 /* height */, GetParam() /* cross_process */);

    blink::WebFindOptions options;
    Find("result", options);
    options.findNext = true;
    Find("result", options);
    Find("result", options);
    Find("result", options);
    Find("result", options);
    delegate()->WaitForFinalReply();

    FindResults results = delegate()->GetFindResults();
    EXPECT_EQ(last_request_id(), results.request_id);
    EXPECT_EQ(21, results.number_of_matches);
    EXPECT_EQ(5, results.active_match_ordinal);

    // Add a frame. It contains 5 new matches.
    std::string url = embedded_test_server()->GetURL(
                                                GetParam() ? "b.com" : "a.com", "/find_in_simple_page.html")
                          .spec();
    std::string script = std::string() + "var frame = document.createElement('iframe');" + "frame.src = '" + url + "';" + "document.body.appendChild(frame);";
    delegate()->MarkNextReply();
    ASSERT_TRUE(ExecuteScript(shell(), script));
    delegate()->WaitForNextReply();

    // The number of matches should update automatically to include the matches
    // from the newly added frame.
    results = delegate()->GetFindResults();
    EXPECT_EQ(26, results.number_of_matches);
    EXPECT_EQ(5, results.active_match_ordinal);
}

// Tests adding a frame during a find session where there were previously no
// matches.
IN_PROC_BROWSER_TEST_F(FindRequestManagerTest, MAYBE(AddFrameAfterNoMatches))
{
    TestNavigationObserver navigation_observer(contents());
    NavigateToURL(shell(), GURL("about:blank"));
    EXPECT_TRUE(navigation_observer.last_navigation_succeeded());

    blink::WebFindOptions default_options;
    Find("result", default_options);
    delegate()->WaitForFinalReply();

    // Initially, there are no matches on the page.
    FindResults results = delegate()->GetFindResults();
    EXPECT_EQ(last_request_id(), results.request_id);
    EXPECT_EQ(0, results.number_of_matches);
    EXPECT_EQ(0, results.active_match_ordinal);

    // Add a frame. It contains 5 new matches.
    std::string url = embedded_test_server()->GetURL("/find_in_simple_page.html").spec();
    std::string script = std::string() + "var frame = document.createElement('iframe');" + "frame.src = '" + url + "';" + "document.body.appendChild(frame);";
    delegate()->MarkNextReply();
    ASSERT_TRUE(ExecuteScript(shell(), script));
    delegate()->WaitForNextReply();

    // The matches from the new frame should be found automatically, and the first
    // match in the frame should be activated.
    results = delegate()->GetFindResults();
    EXPECT_EQ(5, results.number_of_matches);
    EXPECT_EQ(1, results.active_match_ordinal);
}

// Tests a frame navigating to a different page during a find session.
IN_PROC_BROWSER_TEST_P(FindRequestManagerTest, MAYBE(NavigateFrame))
{
    LoadMultiFramePage(2 /* height */, GetParam() /* cross_process */);

    blink::WebFindOptions options;
    Find("result", options);
    options.findNext = true;
    options.forward = false;
    Find("result", options);
    Find("result", options);
    Find("result", options);
    delegate()->WaitForFinalReply();

    FindResults results = delegate()->GetFindResults();
    EXPECT_EQ(last_request_id(), results.request_id);
    EXPECT_EQ(21, results.number_of_matches);
    EXPECT_EQ(19, results.active_match_ordinal);

    // Navigate one of the empty frames to a page with 5 matches.
    FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())->GetFrameTree()->root();
    GURL url(embedded_test_server()->GetURL(
        GetParam() ? "b.com" : "a.com", "/find_in_simple_page.html"));
    delegate()->MarkNextReply();
    TestNavigationObserver navigation_observer(contents());
    NavigateFrameToURL(root->child_at(0)->child_at(1)->child_at(0), url);
    EXPECT_TRUE(navigation_observer.last_navigation_succeeded());
    delegate()->WaitForNextReply();

    // The navigation results in an extra reply before the one we care about. This
    // extra reply happens because the RenderFrameHost changes before it navigates
    // (because the navigation is cross-origin). The first reply will not change
    // the number of matches because the frame that is navigating was empty
    // before.
    if (delegate()->GetFindResults().number_of_matches == 21) {
        delegate()->MarkNextReply();
        delegate()->WaitForNextReply();
    }

    // The number of matches and the active match ordinal should update
    // automatically to include the new matches.
    results = delegate()->GetFindResults();
    EXPECT_EQ(26, results.number_of_matches);
    EXPECT_EQ(24, results.active_match_ordinal);
}

// Tests Searching in a hidden frame. Matches in the hidden frame should be
// ignored.
IN_PROC_BROWSER_TEST_F(FindRequestManagerTest, MAYBE(HiddenFrame))
{
    LoadAndWait("/find_in_hidden_frame.html");

    blink::WebFindOptions default_options;
    Find("hello", default_options);
    delegate()->WaitForFinalReply();
    FindResults results = delegate()->GetFindResults();

    EXPECT_EQ(last_request_id(), results.request_id);
    EXPECT_EQ(1, results.number_of_matches);
    EXPECT_EQ(1, results.active_match_ordinal);
}

// Tests that new matches can be found in dynamically added text.
IN_PROC_BROWSER_TEST_P(FindRequestManagerTest, MAYBE(FindNewMatches))
{
    LoadAndWait("/find_in_dynamic_page.html");

    blink::WebFindOptions options;
    Find("result", options);
    options.findNext = true;
    Find("result", options);
    Find("result", options);
    delegate()->WaitForFinalReply();

    FindResults results = delegate()->GetFindResults();
    EXPECT_EQ(last_request_id(), results.request_id);
    EXPECT_EQ(3, results.number_of_matches);
    EXPECT_EQ(3, results.active_match_ordinal);

    // Dynamically add new text to the page. This text contains 5 new matches for
    // "result".
    ASSERT_TRUE(ExecuteScript(contents()->GetMainFrame(), "addNewText()"));

    Find("result", options);
    delegate()->WaitForFinalReply();

    results = delegate()->GetFindResults();
    EXPECT_EQ(last_request_id(), results.request_id);
    EXPECT_EQ(8, results.number_of_matches);
    EXPECT_EQ(4, results.active_match_ordinal);
}

IN_PROC_BROWSER_TEST_F(FindRequestManagerTest, MAYBE(FindInPage_Issue627799))
{
    LoadAndWait("/find_in_long_page.html");

    blink::WebFindOptions options;
    Find("42", options);
    delegate()->WaitForFinalReply();

    FindResults results = delegate()->GetFindResults();
    EXPECT_EQ(last_request_id(), results.request_id);
    EXPECT_EQ(970, results.number_of_matches);
    EXPECT_EQ(1, results.active_match_ordinal);

    delegate()->StartReplyRecord();
    options.findNext = true;
    options.forward = false;
    Find("42", options);
    delegate()->WaitForFinalReply();

    // This is the crux of the issue that this test guards against. Searching
    // across the frame boundary should not cause the frame to be re-scoped. If
    // the re-scope occurs, then we will see the number of matches change in one
    // of the recorded find replies.
    for (auto& reply : delegate()->GetReplyRecord()) {
        EXPECT_EQ(last_request_id(), reply.request_id);
        EXPECT_TRUE(reply.number_of_matches == kInvalidId || reply.number_of_matches == results.number_of_matches);
    }
}

IN_PROC_BROWSER_TEST_F(FindRequestManagerTest, MAYBE(FindInPage_Issue644448))
{
    TestNavigationObserver navigation_observer(contents());
    NavigateToURL(shell(), GURL("about:blank"));
    EXPECT_TRUE(navigation_observer.last_navigation_succeeded());

    blink::WebFindOptions default_options;
    Find("result", default_options);
    delegate()->WaitForFinalReply();

    // Initially, there are no matches on the page.
    FindResults results = delegate()->GetFindResults();
    EXPECT_EQ(last_request_id(), results.request_id);
    EXPECT_EQ(0, results.number_of_matches);
    EXPECT_EQ(0, results.active_match_ordinal);

    // Load a page with matches.
    LoadAndWait("/find_in_simple_page.html");

    Find("result", default_options);
    delegate()->WaitForFinalReply();

    // There should now be matches found. When the bug was present, there were
    // still no matches found.
    results = delegate()->GetFindResults();
    EXPECT_EQ(last_request_id(), results.request_id);
    EXPECT_EQ(5, results.number_of_matches);
}

#if defined(OS_ANDROID)
// Tests requesting find match rects.
IN_PROC_BROWSER_TEST_F(FindRequestManagerTest, MAYBE(FindMatchRects))
{
    LoadAndWait("/find_in_page.html");

    blink::WebFindOptions default_options;
    Find("result", default_options);
    delegate()->WaitForFinalReply();
    EXPECT_EQ(19, delegate()->GetFindResults().number_of_matches);

    // Request the find match rects.
    contents()->RequestFindMatchRects(-1);
    delegate()->WaitForMatchRects();
    const std::vector<gfx::RectF>& rects = delegate()->find_match_rects();

    // The first match should be active.
    EXPECT_EQ(rects[0], delegate()->active_match_rect());

    // All results after the first two should be between them in find-in-page
    // coordinates. This is because results 2 to 19 are inside an iframe located
    // between results 0 and 1. This applies to the fixed div too.
    EXPECT_LT(rects[0].y(), rects[1].y());
    for (int i = 2; i < 19; ++i) {
        EXPECT_LT(rects[0].y(), rects[i].y());
        EXPECT_GT(rects[1].y(), rects[i].y());
    }

    // Result 3 should be below results 2 and 4. This is caused by the CSS
    // transform in the containing div. If the transform doesn't work then result
    // 3 will be between results 2 and 4.
    EXPECT_GT(rects[3].y(), rects[2].y());
    EXPECT_GT(rects[3].y(), rects[4].y());

    // Results 6, 7, 8 and 9 should be one below the other in that same order. If
    // overflow:scroll is not properly handled then result 8 would be below result
    // 9 or result 7 above result 6 depending on the scroll.
    EXPECT_LT(rects[6].y(), rects[7].y());
    EXPECT_LT(rects[7].y(), rects[8].y());
    EXPECT_LT(rects[8].y(), rects[9].y());

    // Results 11, 12, 13 and 14 should be between results 10 and 15, as they are
    // inside the table.
    EXPECT_GT(rects[11].y(), rects[10].y());
    EXPECT_GT(rects[12].y(), rects[10].y());
    EXPECT_GT(rects[13].y(), rects[10].y());
    EXPECT_GT(rects[14].y(), rects[10].y());
    EXPECT_LT(rects[11].y(), rects[15].y());
    EXPECT_LT(rects[12].y(), rects[15].y());
    EXPECT_LT(rects[13].y(), rects[15].y());
    EXPECT_LT(rects[14].y(), rects[15].y());

    // Result 11 should be above results 12, 13 and 14 as it's in the table
    // header.
    EXPECT_LT(rects[11].y(), rects[12].y());
    EXPECT_LT(rects[11].y(), rects[13].y());
    EXPECT_LT(rects[11].y(), rects[14].y());

    // Result 11 should also be right of results 12, 13 and 14 because of the
    // colspan.
    EXPECT_GT(rects[11].x(), rects[12].x());
    EXPECT_GT(rects[11].x(), rects[13].x());
    EXPECT_GT(rects[11].x(), rects[14].x());

    // Result 12 should be left of results 11, 13 and 14 in the table layout.
    EXPECT_LT(rects[12].x(), rects[11].x());
    EXPECT_LT(rects[12].x(), rects[13].x());
    EXPECT_LT(rects[12].x(), rects[14].x());

    // Results 13, 12 and 14 should be one above the other in that order because
    // of the rowspan and vertical-align: middle by default.
    EXPECT_LT(rects[13].y(), rects[12].y());
    EXPECT_LT(rects[12].y(), rects[14].y());

    // Result 16 should be below result 15.
    EXPECT_GT(rects[15].y(), rects[14].y());

    // Result 18 should be normalized with respect to the position:relative div,
    // and not it's immediate containing div. Consequently, result 18 should be
    // above result 17.
    EXPECT_GT(rects[17].y(), rects[18].y());
}

// Tests activating the find match nearest to a given point.
IN_PROC_BROWSER_TEST_F(FindRequestManagerTest,
    MAYBE(ActivateNearestFindMatch))
{
    LoadAndWait("/find_in_page.html");

    blink::WebFindOptions default_options;
    Find("result", default_options);
    delegate()->WaitForFinalReply();
    EXPECT_EQ(19, delegate()->GetFindResults().number_of_matches);

    // Get the find match rects.
    contents()->RequestFindMatchRects(-1);
    delegate()->WaitForMatchRects();
    const std::vector<gfx::RectF>& rects = delegate()->find_match_rects();

    // Activate matches via points inside each of the find match rects, in an
    // arbitrary order. Check that the correct match becomes active after each
    // activation.
    int order[19] = { 11, 13, 2, 0, 16, 5, 7, 10, 6, 1, 15, 14, 9, 17, 18, 3, 8, 12, 4 };
    for (int i = 0; i < 19; ++i) {
        delegate()->MarkNextReply();
        contents()->ActivateNearestFindResult(
            rects[order[i]].CenterPoint().x(), rects[order[i]].CenterPoint().y());
        delegate()->WaitForNextReply();
        EXPECT_EQ(order[i] + 1, delegate()->GetFindResults().active_match_ordinal);
    }
}
#endif // defined(OS_ANDROID)

} // namespace content
